<?php
/**
 *  专门处理excel上传安全的类。
 *
 *  Copyright(c) 2012 by yuhui. All rights reserved
 *
 *  To contact the author write to {@link mailto:yuhui@xiaomi.com}
 *
 * @author yuhui
 * @version Id: ExcelUpload.class.php,12-11-22 上午11:25
 * @package
 */
require_once __SYS__.'lib/phpexcel/PHPExcel.php';

class ExcelUpload
{

    public $file_name  = "";    //文件名字。
    public $file_tmp   = "";    //文件临时存放路径
    public $file_size  = "";    //文件大小。
    public $file_type  = "";    //文件类型。
    public $file_error = "";    //文件错误信息
    private $file_base_name = ''; // 文件基础名，如ExcelUpload.class.php的ExcelUpload.class
    private $file_ext       = "";    //通过截取字符串获取的文件后缀。
    private $file_mime      = "";    //文件的mime
    private $file_mime_ext  = "";    //通过文件的mime判断获取的文件后缀。
    public $error_msg       = "";

    /**
     * php excel reader
     *
     * @var object
     */
    protected $phpexcel_reader;

    /**
     * php excel object
     *
     * @var object
     */
    protected $phpexcel_obj;
    //excel 的mime配置。
    public $allow_mime_cfg = array(
        'xls'  => array(
            'application/excel',
            'application/vnd.ms-excel',
            'application/vnd.ms-office',
            'application/msexcel',
            "application/msword" //ubuntu下
        ),
        'xlsx' => array(
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        ),
        'csv'  => array(
            'text/x-comma-separated-values',
            'text/comma-separated-values',
            'application/octet-stream',
            'application/vnd.ms-excel',
            'application/x-csv',
            'text/x-csv',
            'text/csv',
            'application/csv',
            'application/excel',
            'application/vnd.msexcel',
            "text/plain"
        )
    );
    //zip&excel在centos下的mime
    public static $zip_cfg = array(
        "application/zip",
        "application/x-zip",
        "application/x-zip-compressed"
    );
    //excle的后缀。
    public $excle_fix_cfg = array(
        "xlsx",
        "xls",
        "csv"
    );

    /**
     * 构造方法
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 上午11:27
     * @param  array    $file       上传文件变量$_FILES
     * @param  bool     $allow_fix  允许上传的格式。不传则默认为xls和xlsx.
     * @return void Description
     */
    public function __construct($file, $allow_fix = array("xls", "xlsx"))
    {
        $this->init($file);
        //生成允许的mime配置
        foreach ($this->allow_mime_cfg as $k => $v)
        {
            if (!in_array($k, $allow_fix))
            {
                unset($this->allow_mime_cfg[$k]);
            }
        }
        //生成允许的文件格式
        foreach ($this->excle_fix_cfg as $k => $v)
        {
            if (!in_array($v, $allow_fix))
            {
                unset($this->excle_fix_cfg[$k]);
            }
        }
    }

    /**
     * 初始化文件属性，并判断是否上传成功。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 上午11:36
     * @param  array $file 上传文件的$_FILES['file'] 'file' 为表单提交的变量。
     */
    public function init($file)
    {
        $this->file_name  = isset($file['name']) ? (string) $file['name'] : "";
        $this->file_tmp   = isset($file['tmp_name']) ? (string) $file['tmp_name'] : "";
        $this->file_size  = isset($file['size']) ? (int) $file['size'] : "";
        $this->file_type  = isset($file['type']) ? (string) $file['type'] : "";
        $this->file_error = isset($file['error']) ? (int) $file['error'] : "NO";

        $path_info            = $this->pathInfo($this->file_name);
        $this->file_ext       = $path_info['extension'];
        $this->file_base_name = $path_info['filename'];
        $this->file_mime      = $this->getFileMimeType();
    }

    /**
     * 3步判断其是否是安全上传成功。（此函数帮助一次性调用判断，但可能某一步出错，需单独调试）
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午4:40
     * @return boolean
     */
    public function isSecurityUpload()
    {
        if ($this->isUploadSuccess() && $this->isPostUpload() && $this->isRightMime())
        {
            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }

    /**
     * 判断是否上传成功并返回错误的原因。调用此方法前需要先初始化调用init方法。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 上午11:59
     * @return string|boolean
     */
    public function isUploadSuccess()
    {
        if ($this->file_error === "NO")
        {
            $this->error_msg = "文件上传失败";
            return FALSE;
        }
        else
        {
            if ($this->file_error != UPLOAD_ERR_OK)
            {
                switch ($this->file_error)
                {
                    case 1:
                    case 2:
                        $this->error_msg = "文件太大,超过服务器配置。";
                        return FALSE;
                        break;
                    case 3:
                        $this->error_msg = "文件部分上传失败";
                        return FALSE;
                        break;
                    case 4:
                        $this->error_msg = "获取不到上传文件";
                        return FALSE;
                        break;
                    default :
                        $this->error_msg = "文件上传失败";
                        return FALSE;
                        break;
                }
            }
        }

        return TRUE;
    }

    /**
     * 是否通过post方法上传的合法文件。调用此方法前需要先初始化调用init方法。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午12:09
     * @return string|boolean
     */
    public function isPostUpload()
    {
        if (!@is_uploaded_file($this->file_tmp))
        {
            $this->error_msg = "文件不是通过post方法上传";
            return FALSE;
        }
        else
        {
            return TRUE;
        }
    }

    /**
     * 取得文件MIME类型,调用此方法前需要先初始化调用init方法。
     *
     * @param array
     * @return string
     */
    public function getFileMimeType()
    {
        // 合法的格式
        $regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';

        if (function_exists('finfo_file'))
        {
            $finfo = finfo_open(FILEINFO_MIME);
            if (is_resource($finfo))
            {
                $mime = @finfo_file($finfo, $this->file_tmp);
                finfo_close($finfo);

                // 格式对了才能返回
                if (is_string($mime) && preg_match($regexp, $mime, $matches))
                {
                    return $matches[1];
                }
            }
        }

        // 对于linux系统需要单独处理
        if (DIRECTORY_SEPARATOR !== '\\')
        {
            $cmd = 'file --brief --mime ' . escapeshellarg($this->file_tmp) . ' 2>&1';

            if (function_exists('exec'))
            {
                $mime = @exec($cmd, $mime, $return_status);
                if ($return_status === 0 && is_string($mime) && preg_match($regexp, $mime, $matches))
                {
                    return $matches[1];
                }
            }

            if ((bool) @ini_get('safe_mode') === FALSE && function_exists('shell_exec'))
            {
                $mime = @shell_exec($cmd);
                if (strlen($mime) > 0)
                {
                    $mime = explode("\n", trim($mime));
                    if (preg_match($regexp, $mime[(count($mime) - 1)], $matches))
                    {
                        return $matches[1];
                    }
                }
            }

            if (function_exists('popen'))
            {
                $proc = @popen($cmd, 'r');
                if (is_resource($proc))
                {
                    $mime = @fread($proc, 512);
                    @pclose($proc);
                    if ($mime !== FALSE)
                    {
                        $mime = explode("\n", trim($mime));
                        if (preg_match($regexp, $mime[(count($mime) - 1)], $matches))
                        {
                            return $matches[1];
                        }
                    }
                }
            }
        }

        // 如果有mime_content_type就用它吧
        if (function_exists('mime_content_type'))
        {
            $file_type = @mime_content_type($this->file_tmp);
            if (strlen($file_type) > 0)
            {
                return $file_type;
            }
        }

        // 实在没招了，就用file里的type
        return $this->file_type;
    }

    /**
     * 判断是否是正确的excel的mime。调用此方法前需要先初始化调用init方法。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午12:26
     * @return bool
     */
    public function isRightMime()
    {
        $real_ext = "";
        foreach ($this->allow_mime_cfg as $k => $v)
        {
            if (in_array($this->file_mime, $v))
            {
                $real_ext = $k;
            }
        }

        //判断文件类型
        if (!in_array($real_ext, $this->excle_fix_cfg))
        {
            // 如果是zip类型，看看后缀[主要应用于centos系统]。
            $zip_arr = self::$zip_cfg;
            $ext_arr = $this->excle_fix_cfg;
            if (in_array($this->file_mime, $zip_arr) && in_array($this->file_ext, $ext_arr))
            {
                return TRUE;
            }
            else
            {
                $this->error_msg = "文件类型错误";
                return FALSE;
            }
        }
        else
        {
            return TRUE;
        }
    }

    /**
     * 获得文件的大小。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午4:32
     * @return type
     */
    public function getFileSize()
    {
        return $this->file_size;
    }

    /**
     * 获得上传文件名的后缀。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午4:32
     * @return type
     */
    public function getFileExt()
    {
        return $this->file_ext;
    }

    /**
     * 判断文件大小
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-22 下午4:49
     * @param  type $m 最大限制的大小（默认为2M）
     * @return boolean 超过返回true,未超返回false;
     */
    public function isOutOfMaxSize($m = 2)
    {
        $file_xls_size_max = (int) $m * 1024 * 1024;
        if ($this->file_size > $file_xls_size_max)
        {
            return TRUE;
        }
        else
        {
            $this->error_msg = "文件大小超过最大限制值";
            return FALSE;
        }
    }

    /**
     * 获取错误信息。
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  12-11-23 上午10:37
     * @return void Description
     */
    public function getErrorMessage()
    {
        return $this->error_msg;
    }

    /**
     * 获取文件上传临时保存文件的路径。[用来移动上传成功的文件]
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  13-2-28 上午11:13
     * @return String
     */
    public function getFileTmpPath()
    {
        return $this->file_tmp;
    }

    public function getFileTmpName()
    {
        $path_info = pathinfo($this->file_tmp);
        return $path_info['filename'] . $path_info['extension'];
    }

    /**
     * 获取文件名称
     * @author Cash yu <yuhui@xiaomi.com>
     * @since  13-2-28 上午11:13
     * @return String
     */
    public function getFileName()
    {
        return $this->file_name;
    }

    /**
     * 获取文件基础名称
     *
     * @return string
     */
    public function getFileBaseName()
    {
        return $this->file_base_name;
    }

    /**
     * php excel 操作类，必须要在操作excel后才能调用此类
     */
    public function getPhpExcelObj()
    {
        if (empty($this->phpexcel_obj))
        {
            if (empty($this->phpexcel_reader))
            {
                $beta                  = $this->file_ext != 'xlsx' ? 'Excel5' : 'Excel2007';
                $this->phpexcel_reader = PHPExcel_IOFactory::createReader($beta);
            }
            $this->phpexcel_obj = $this->phpexcel_reader->load($this->file_tmp);
        }

        return $this->phpexcel_obj;
    }

    /**
     * 获取excel内的信息数据
     *
     * @author kuangshunping
     * @param  array $fields_map
     * array(
     *     'A' => 'goods_id',
     *     'B' => 'goods_name'
     * )
     * @param  int   $start      数据开始的行数
     * @return array 返回的数组每行除了fileds_map中的值之外，会增加一个row_index索引记录是excel的第几行
     */
    public function getExcelDataList(array $fields_map, $start = 2)
    {
        Loader::loadLib('PHPExcel', 'phpexcel', '.php');
        $data_list = array();
        try
        {
            $phpexcel_obj       = $this->getPhpExcelObj();
            $currentActiveSheet = $phpexcel_obj->getActiveSheet();
            $rowIterator        = $currentActiveSheet->getRowIterator();

            // 循环行获取数据
            foreach ($rowIterator as $row)
            {
                $row_index = $row->getRowIndex();
                if ($row_index >= $start)
                {
                    $tmp = array();

                    foreach ($fields_map as $k => $v)
                    {
                        $tmp[$v] = trim($currentActiveSheet->getCell("{$k}{$row_index}")->getCalculatedValue()); //->getValue());
                    }

                    $callback = function($val){
                        if ($val === '')
                        {
                            return false;
                        }
                        return true;
                    };
                    // 无数据则跳出循环
                    if (!array_filter($tmp, $callback))
                    {
                        break;
                    }

                    $data_list[]      = $tmp;
                }
            }
            return $data_list;
        }
        catch (Exception $e)
        {
            $this->error_msg = '上传文件格式无法解析，请尝试其他格式。详细信息：' . $e->getMessage();
            return false;
        }
    }

    /**
     * 获取excel内的多个sheet的信息数据
     *
     * @param       array       $fields_map     格式如：array(
     *                                                  'A' => 'goods_id',
     *                                                  'B' => 'goods_name'
     *                                                  )
     * @param       int         $start          数据开始的行数
     * @param       bool        $remove_empty_end_lines  是否删除最后的空行
     * @return      array                       返回的数组每行除了fileds_map中的值之外，会增加一个row_index索引记录是excel的第几行
     */
    public function getExcelSheetDataList(array $fields_map, $start = 2, $remove_empty_end_lines = false)
    {
        Loader::loadLib('PHPExcel', 'phpexcel', '.php');
        $data_list = array();
        try
        {
            $phpexcel_obj = $this->getPhpExcelObj();
            $sheets       = $phpexcel_obj->getAllSheets();
            foreach ($sheets as $currentActiveSheet)
            {
                $rowIterator = $currentActiveSheet->getRowIterator();

                // 循环行获取数据
                foreach ($rowIterator as $row)
                {
                    $row_index = $row->getRowIndex();
                    if ($row_index >= $start)
                    {
                        $tmp = array();

                        foreach ($fields_map as $k => $v)
                        {
                            $tmp[$v] = trim($currentActiveSheet->getCell("{$k}{$row_index}")->getCalculatedValue()); //->getValue());
                        }

                        $tmp['row_index'] = $row_index;
                        $data_list[]      = $tmp;
                    }
                }
                if ($remove_empty_end_lines)
                {
                    $data_list = $this->removeEmptyEndLines($data_list);
                }
                $all_data_list[] = $data_list;
                unset($data_list);
            }
            return $all_data_list;
        }
        catch (Exception $e)
        {
            $this->error_msg = '上传文件格式无法解析，请尝试其他格式。详细信息：' . $e->getMessage();
            return false;
        }
    }

    /**
     * 获取指定的行记录
     *
     * @param   int     $line_index
     * @return  array
     */
    public function getExcelLineData($line_index)
    {
        $data_list = array();
        try
        {
            $phpexcel_obj       = $this->getPhpExcelObj();
            $currentActiveSheet = $phpexcel_obj->getActiveSheet();
            $rowIterator        = $currentActiveSheet->getRowIterator();

            // 循环行获取数据
            foreach ($rowIterator as $row)
            {
                $cellIterator = $row->getCellIterator();
                $row_index    = $row->getRowIndex();
                if ($row_index == $line_index)
                {
                    foreach ($cellIterator as $cell)
                    {
                        $column             = $cell->getColumn();
                        $data_list[$column] = $cell->getCalculatedValue();
                    }
                }
            }

            unset($phpexcel_obj);
            return $data_list;
        }
        catch (Exception $e)
        {
            Response::errorCallBack('上传文件格式无法解析，请尝试其他格式');
        }
    }

    /**
     * 获得工作表的最大列，返回列的名称
     * yangyangliu
     */
    public function getLastColName()
    {
        Loader::loadLib('PHPExcel', 'phpexcel', '.php');
        try
        {
            $phpexcel_obj = $this->getPhpExcelObj();

            $last_col_name = $phpexcel_obj->getActiveSheet()->getHighestColumn();
            return $last_col_name;
        }
        catch (Exception $e)
        {
            Response::errorCallBack('上传文件格式无法解析，请尝试其他格式');
        }
    }

    /**
     * 获取CSV文件信息
     *
     * @author zhouhongwei
     * @param array $fields_map
     *              格式如：
     *              array(
     *            	0=>'goods_id',
     *                  2=>'goods_name',
     *               )
     * @param int $start  数据开始的行数
     * @param bool 是否删除最后的空行
     * @return array
     */
    public function getCSVDataList(array $fields_map, $start = 2, $remove_empty_end_lines = false)
    {
        try
        {
            $f = @fopen($this->file_tmp, 'r');
            if (!$f)
            {
                throw new Exception("获取上传文件失败");
            }
            $index     = 0;
            $data_list = array();
            while (!feof($f))
            {
                $index ++;
                $line = trim(fgets($f));
                if ($index < $start)
                {
                    continue;
                }
                $line_arr = explode(",", $line);
                $tmp      = array();
                foreach ($fields_map as $k => $v)
                {
                    $tmp[$v] = isset($line_arr[$k]) ? $line_arr[$k] : '';
                }
                $tmp['row_index'] = $index;
                $data_list []     = $tmp;
            }
            if ($remove_empty_end_lines)
            {
                $data_list = $this->removeEmptyEndLines($data_list);
            }
            return $data_list;
        }
        catch (Exception $e)
        {
            Response::errorCallBack($e->getMessage());
        }
    }

    private function pathInfo($filepath)
    {
        $path_parts               = array();
        $path_parts ['dirname']   = substr($filepath, 0, strrpos($filepath, '/'));
        $path_parts ['basename']  = substr($filepath, strrpos($filepath, '/'));
        $path_parts ['extension'] = substr(strrchr($filepath, '.'), 1);
        $path_parts ['filename']  = substr($path_parts ['basename'], 0, strrpos($path_parts ['basename'], '.'));
        return $path_parts;
    }

}
